package br.edu.ufcg.msnlab2.grupo07;

import java.util.HashMap;
import java.util.Map;

import junit.framework.TestCase;
import mathExpr.parser.javacc.ParseException;
import br.edu.ufcg.msnlab2.InvalidFunctionException;
import br.edu.ufcg.msnlab2.MSNLabException;
import br.edu.ufcg.msnlab2.grupo07.monteCarlo.MonteCarloResult;
import br.edu.ufcg.msnlab2.grupo07.monteCarlo.PlainMonteCarloSolverImpl;
import br.edu.ufcg.msnlab2.grupo07.monteCarlo.Tuple;
import br.edu.ufcg.msnlab2.misc.Function;
import br.edu.ufcg.msnlab2.misc.FunctionImpl;

/**
 * This class contains unit tests for the simple Monte Carlo method
 * that is implemented by the MonteCarloSolverImpl class.
 * @author David Candeia
 * @author Felipe Leal
 *
 */
public class PlainMonteCarloSolverImplTest extends TestCase {
	
	private PlainMonteCarloSolverImpl simpleSolver;
	
	public void setUp(){
		simpleSolver = new PlainMonteCarloSolverImpl();
		assertNotNull(simpleSolver);
	}
	
	/**
	 * This method contains tests for the solve method that is called
	 * by the interface in order to request the utilization of the 
	 * Monte Carlo method. Functions with one variable are tested.
	 * @throws ParseException Exception thrown if an invalid function expression is being parsed
	 * @throws MSNLabException Exception thrown if any invalid condition is reached while calculating the
	 * integral using simple Monte Carlo method
	 *  
	 */
	public void testSolve() throws ParseException, MSNLabException{
		
		//Calculating PI		
		Function function = new FunctionImpl("(1-x^2)^(1/2)");

		Map<String, Tuple<Double, Double>> limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(-1.0d, 1.0d));
		
		MonteCarloResult result = simpleSolver.solve(function, limits, 1000);
		Double pi = 2 * result.getIntegrationResult();
		assertEquals(pi, Math.PI, 0.03);
		assertEquals(result.getError(), 0.014310464506060517);
		
		//Another function. Correct value: 2516.8056708970594
		function = new FunctionImpl("x^3 + 1/x + x^(1/2)");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(2.0d, 10.0d));

		result = simpleSolver.solve(function, limits, 100);
		assertEquals(result.getIntegrationResult(), 1935.6882974681919, 0.03);
		assertEquals(result.getError(), 213.48420873568833);
		
		//More points enhance the estimative.
		function = new FunctionImpl("x^3 + 1/x + x^(1/2)");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(2.0d, 10.0d));

		result = simpleSolver.solve(function, limits, 10000);
		assertEquals(result.getIntegrationResult(), 2515.017778592621, 0.03);
		assertEquals(result.getError(), 22.829180624600365);
		
		//Using an invalid function expression
		function = new FunctionImpl("3.14*x*((x)^(1/2))");
		
		try{
			simpleSolver.solve(null, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		//Invalid limits
		try{
			simpleSolver.solve(function, null, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			simpleSolver.solve(function, new HashMap<String, Tuple<Double,Double>>(), 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("h", new Tuple<Double, Double>(0.0d, 1.0d));
		try{
			simpleSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			simpleSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
	}

	/**
	 * This method contains tests for the solve method that is called
	 * by the interface in order to request the utilization of the 
	 * Monte Carlo method. Functions with two or more variables are tested.
	 * @throws ParseException Exception thrown if an invalid function expression is being parsed
	 * @throws MSNLabException Exception thrown if any invalid condition is reached while calculating the
	 * integral using simple Monte Carlo method
	 *  
	 */
	public void testSolverWithMultipleVariables() throws ParseException, MSNLabException{
		//Test with two variables. More points improve the estimation and decreases the error! CORRECT VALUE: 0.30023		
		Function function = new FunctionImpl("sin(3.14*x*y)*((x*y)^(1/2))");

		Map<String, Tuple<Double,Double>> limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(0.0d, 1.0d));
		limits.put("y", new Tuple<Double, Double>(0.0d, 1.0d));
		
		MonteCarloResult result2 = simpleSolver.solve(function, limits, 10000);
		assertEquals(result2.getIntegrationResult(), 0.30022, 0.01);
		assertEquals(result2.getError(), 0.0025627568358906755);

		
		//Very small number of points. Large error and bad estimative
		result2 = simpleSolver.solve(function, limits, 10);
		assertEquals(result2.getIntegrationResult(), 0.20769311736031612, 0.01);
		assertEquals(result2.getError(), 0.06084435601261715);
		
		//Another function. CORRECT VALUE: 1.9983513245889003*^30
		function = new FunctionImpl("3^(x*y) + 10*x - 1/y");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(3.0d, 7.5d));
		limits.put("y", new Tuple<Double, Double>(2.0d, 9.0d));
		
		result2 = simpleSolver.solve(function, limits, 8000);
		assertEquals(result2.getIntegrationResult(), 2.2447818074536146E30, 0.01);
		assertEquals(result2.getError(), 5.915530920269153E29);
		
		//Another function. Correct value: 1220.2627703681487
		function = new FunctionImpl("sin(x)+cos(1/y)");
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(-1.0d, 10.0d));
		limits.put("y", new Tuple<Double, Double>(1.0d, 100.0d));
		
		MonteCarloResult result = simpleSolver.solve(function, limits, 80);
		assertEquals(result.getIntegrationResult(), 1356.5198757992214, 0.01);
		assertEquals(result.getError(), 79.19609891336245);
		
		//Using an invalid function expression
		function = new FunctionImpl("3.14*x*y*((x*y)^(1/2))");
		
		try{
			simpleSolver.solve(null, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		//Invalid limits
		try{
			simpleSolver.solve(function, null, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			simpleSolver.solve(function, new HashMap<String, Tuple<Double,Double>>(), 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("h", new Tuple<Double, Double>(0.0d, 1.0d));
		try{
			simpleSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			simpleSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
	}
}
